package at.ac.tuwien.prog.command.factory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import at.ac.tuwien.prog.command.AddConstantCommand;
import at.ac.tuwien.prog.command.FlashReadCommand;
import at.ac.tuwien.prog.command.ICommand;
import at.ac.tuwien.prog.command.ReadCommand;
import at.ac.tuwien.prog.command.SetConstantCommand;
import at.ac.tuwien.prog.command.SetValueCommand;

public class CommandFactoryImpl implements ICommandFactory {
	
	private static final Log LOG = LogFactory.getLog(CommandFactoryImpl.class);

	private static final Map<Class<? extends ICommand>, Integer> IDS_BY_COMMANDS;
	private static final Map<Integer, Class<? extends ICommand>> COMMANDS_BY_IDS;
	private static final Map<String, Class<? extends ICommand>> COMMANDS_BY_NAME;
	
	
	
	static {
		final List<Class<? extends ICommand>> commands = new ArrayList<Class<? extends ICommand>>(16);
		// order is important: index will be used as integer written into 4bit command header
		commands.add(SetConstantCommand.class); // 1 setc
		commands.add(SetValueCommand.class); // 2 setv
//		commands.add(null); // 3 setic
//		commands.add(null); // 4 setiv
		commands.add(AddConstantCommand.class); // 5 addc
//		commands.add(null); // 6 addpcc
//		commands.add(null); // 7 addv
//		commands.add(null); // 8 subv
//		commands.add(null); // 9 mulc
//		commands.add(null); // 10 mulv
//		commands.add(null); // 11 divc
//		commands.add(null); // 12 divv
//		commands.add(null); // 13 jump
		commands.add(ReadCommand.class); // 14 read
//		commands.add(null); // 15 flashw
		commands.add(FlashReadCommand.class); // 16 flashr

		
		
		final Map<Class<? extends ICommand>, Integer> tmpIdsByCommands = new HashMap<Class<? extends ICommand>, Integer>();
		final Map<Integer, Class<? extends ICommand>> tmpCommandsByIds = new HashMap<Integer, Class<? extends ICommand>>();
		final Map<String, Class<? extends ICommand>> tmpCommandsByName = new HashMap<String, Class<? extends ICommand>>();
		
		for (int i = 0; i < commands.size(); i++) {
			final Class<? extends ICommand> clazz = commands.get(i);
			
			tmpIdsByCommands.put(clazz, i + 1);
			tmpCommandsByIds.put(i + 1, clazz);
			tmpCommandsByName.put(getStaticCommandName(clazz), clazz);
		}
		
		IDS_BY_COMMANDS = Collections.unmodifiableMap(tmpIdsByCommands);
		COMMANDS_BY_IDS = Collections.unmodifiableMap(tmpCommandsByIds);
		COMMANDS_BY_NAME = Collections.unmodifiableMap(tmpCommandsByName);

		
		
	}
	
	private static String getStaticCommandName(Class<? extends ICommand> clazz) {
		if(clazz == null) {
			throw new IllegalArgumentException("clazz == null");
		}
		try {
			final Field nameField = clazz.getField("NAME");
			return (String) nameField.get(null);
		} catch (Exception e) {
			throw new RuntimeException("Could not get static field 'name' for clazz ["+clazz.getName()+"]!", e);
		}
		
	}
	
	
	
	
	
	public int mapIdByClass(Class<? extends ICommand> clazz) {
		LOG.debug("mapIdByClass(clazz="+clazz+")");
		
		if(clazz == null) {
			throw new IllegalArgumentException("clazz == null");
		}
		final Integer cmdId = IDS_BY_COMMANDS.get(clazz);
		if(cmdId == null) {
			throw new RuntimeException("Could not get ID for command class: " + clazz.getName());
		}
		return cmdId;
	}


	
	private static Class<? extends ICommand> mapClassById(final int id) {
		LOG.debug("mapClassById(id="+id+")");
		
		final Class<? extends ICommand> clazz = COMMANDS_BY_IDS.get(id);
		if(clazz == null) {
			throw new RuntimeException("Could not get command by ID: " + id);
		}
		return clazz;
	}

	
	
	public ICommand newInstanceById(final int commandId, final int operand1, final int operand2) {
		LOG.debug("newInstanceById(commandId=" + commandId + "; operand1=" + operand1 + "; operand2=" + operand2 + ")");

		return this.newInstance(CommandFactoryImpl.mapClassById(commandId), operand1, operand2);
	}
	
	
	public ICommand newInstanceByName(final String commandName, final int operand1, final int operand2) {
		LOG.debug("newInstanceByName(commandName=" + commandName + "; operand1=" + operand1 + "; operand2=" + operand2 + ")");

		final Class<? extends ICommand> cmdClass = COMMANDS_BY_NAME.get(commandName);
		if(cmdClass == null) {
			throw new RuntimeException("Could not get command by name: [" + commandName + "]");
		}
		
		return this.newInstance(cmdClass, operand1, operand2);
	}
	
	private ICommand newInstance(final Class<? extends ICommand> cmdClass, final int operand1, final int operand2) {
		if(cmdClass == null) {
			throw new IllegalArgumentException("cmdClass ==  null");
		}
		try {
			final Constructor<? extends ICommand> cmdConstructor = cmdClass.getConstructor(int.class, int.class);
			return cmdConstructor.newInstance(operand1, operand2);
		} catch(Exception e) {
			throw new RuntimeException("Could not create instance of [" + cmdClass.getName() + "]!", e);
		}
	}
	
}
